/**
 * @file    CTimerDispatcher.cpp
 * @date    2014-06-22
 * @author  Vladimir Prokopovich VProkopovich@gmail.com
 * @brief   Class CTimerDispatcher implementation file.
 */

#include "CTimerDispatcher.h"
#include "HWInterface.h"

extern CTimerDispatcher timerDispatcher;

CTimerDispatcher::CTimerDispatcher()
: mTimersCount(0U)
, mTimerCommands()
, mpNextScheduledTimer(0)
{
   for (unsigned int i = 0U; i < TIMERS_MAX; i++)
   {
      mTimerCommands[i] = 0;
   }
}

CTimerDispatcher::~CTimerDispatcher()
{
   for (int i = mTimersCount-1; i >= 0; i--)
   {
      if (0 != mTimerCommands[i])
      {
         delete mTimerCommands[i];
         mTimerCommands[i] = 0;
         mTimersCount--;
      }
      else
      {
         ;
      }
   }
}

unsigned int CTimerDispatcher::startTimer(const unsigned long long int& intervalOnce, const unsigned long long int& intervalRepeat, void* pData, CTimerCommand::tCallbackFunction callback)
{
   unsigned int retVal = 0U;
   
   if (mTimersCount >= TIMERS_MAX)
   {
      return retVal;
   }

   // Detecting timer type
   CTimerCommand::tTimerType type = CTimerCommand::TIMER_TYPE_INVALID;
   if ((0 != intervalOnce) && (0 != intervalRepeat))
   {
      type = CTimerCommand::TIMER_TYPE_BOTH;
   }
   else if ((0 == intervalOnce) && (0 != intervalRepeat))
   {
      type = CTimerCommand::TIMER_TYPE_REPEAT;
   }
   else if ((0 != intervalOnce) && (0 == intervalRepeat))
   {
      type = CTimerCommand::TIMER_TYPE_ONCE;
   }
   else
   {
      return retVal;
   }
   
   CTimerCommand* pTimer = new CTimerCommand(pData, callback, intervalRepeat, intervalOnce, type);
   if (0 == pTimer)
   {
      return retVal;
   }

   // Adding timer task to array
   mTimerCommands[mTimersCount] = pTimer;
   mTimersCount++;
   
   // Schedule next timer
   scheduleNextTimer();

   return retVal;
}

void CTimerDispatcher::stopTimer(unsigned int id)
{
   bool found = false;
   unsigned int index = 0U;

   for (unsigned int i = 0; i < mTimersCount; i++)
   {
      if (0 != mTimerCommands[i])
      {
         if (id == mTimerCommands[i]->getId())
         {
            found = true;
            index = i;
            break;
         }
      }
      else
      {
      }
   }
   
   if (true == found)
   {
      delete mTimerCommands[index];
      mTimerCommands[index] = 0;
      mTimersCount--;
      
      for (unsigned int i = index; i < mTimersCount; i++)
      {
         mTimerCommands[i] = mTimerCommands[i+1];
      }
      
      scheduleNextTimer();
   }
   else
   {
   }
}

void CTimerDispatcher::processInterrupt()
{
   if (0 != mpNextScheduledTimer)
   {
      mpNextScheduledTimer->timerFired();
      mpNextScheduledTimer->getCallback()(mpNextScheduledTimer->getData());
      
      if (CTimerCommand::TIMER_TYPE_ONCE == mpNextScheduledTimer->getType())
      {
         stopTimer(mpNextScheduledTimer->getId());
         // stopTimer call already contains scheduleNextTimer(), so exiting function here
         return;
      }
   }
   
   scheduleNextTimer();
}

void CTimerDispatcher::scheduleNextTimer()
{
   // Getting id of nearest timer that should fire
   CTimerCommand* pNearestTimer = getNearestTimer();
   if (0 == pNearestTimer)
   {
      return;
   }
   
   mpNextScheduledTimer = pNearestTimer;
   HwTimerStop();
   
   unsigned long long int timeToSchedule = mpNextScheduledTimer->getNextFireTime() - GetMicroSeconds();// FIXME: change operands
   
   HwTimerStart(timeToSchedule);
}

CTimerCommand* CTimerDispatcher::getNearestTimer() const
{
   if (0U == mTimersCount)
   {
      return 0;
   }
   
   unsigned int index = mTimersCount;
   unsigned long long int minFireTime = static_cast<unsigned long long int>(-1); // the biggest value
   
   for (unsigned int i = 0; i < mTimersCount; i++)
   {
      if (0 != mTimerCommands[i])
      {
         if (mTimerCommands[i]->getNextFireTime() < minFireTime)
         {
            index = i;
            minFireTime = mTimerCommands[i]->getNextFireTime();
         }
      }
      else
      {
      }
   }

   return mTimerCommands[index];
}

void HwTimerIsr()
{
   timerDispatcher.processInterrupt();
}
